[org 0x1000] ; ORG在汇编语言中是一条指令，其作用是告诉汇编程序，在开始执行的时候，将某段机器语言装载到内存中的哪个地址

dw 0x55aa; 魔数，用于判断错误

; 打印字符串
mov si, loading
call print

; xchg bx, bx ;断点

detect_memory:
    ; 将ebx置0
    xor ebx, ebx

    mov ax, 0
    mov es, ax
    mov edi, ards_buffer
    mov edx, 0x534d4150 

.next:
    mov eax, 0xe820 ; 子功能号
    mov ecx, 20 ; ards结构的大小（字节）
    int 0x15 ; 系统调用

    jc error ; 如果CF置位，表示出错

    add di, cx ; 将缓存指针指向下一个结构体

    inc word [ards_count] ; 将结构体数量加1

    cmp ebx, 0 ; 0表示检测结束，非0则继续
    jnz .next

    mov si, detecting
    call print

    ; 检测内存完成后，调到转成保护模式
    jmp prepare_protected_mode

prepare_protected_mode:
    xchg bx, bx ; duandian

    cli ; 关闭中断

    ; 打开A20线
    in al , 0x92
    or al , 0b10
    out 0x92, al

    ; 加载GDT， 告诉cpu gdt在哪
    lgdt [gdt_ptr] 

    ; 启动保护模式
    mov eax, cr0
    or eax, 1
    mov cr0, eax

    jmp dword code_selector:protect_mode ; 用跳转来刷新缓存， 启用保护模式

print:
    mov ah, 0x0e
.next:
    mov al, [si]
    cmp al, 0
    jz .done
    int 0x10
    inc si
    jmp .next
.done:
    ret

loading:
    db "Loading Onix...", 10, 13, 0 ; 都是ASCII码，10是换行\n ,13是\r:将光标移到开头
detecting:
    db "Detecting Memory Success...", 10, 13, 0 ; 

error:
    mov si, .msg
    call print
    hlt ; 让cpu停止
    jmp $
    .msg db "Loading Error!!!", 10, 13, 0



[bits 32]
protect_mode:
    ; xchg bx, bx ; 断点
    mov ax, data_selector
    mov ds, ax
    mov es, ax
    mov fs, ax
    mov gs, ax
    mov ss, ax ; 初始化段寄存器

    mov esp, 0x10000 ; 修改栈顶  看一下007.md的内存布局

    mov edi, 0x10000 ; 读取的目标内存
    mov ecx, 10 ; 起始扇区
    mov bl, 200; 扇区数量

    call read_disk

    jmp dword code_selector:0x10000  ; 跳转到0x10000内核开始的地方

    ud2 ; 表示出错，就是我们不可能执行到这个地方，执行到这个地方就报错

jmp $; 阻塞

read_disk:

    ; 设置读写扇区的数量，各端口设置详细参见006硬盘读写文档
    mov dx, 0x1f2
    mov al, bl
    out dx, al

    inc dx; 0x1f3
    mov al, cl ; 起始扇区的前八位
    out dx, al

    inc dx; 0x1f4
    shr ecx, 8 ; 将ecx向右移八位
    mov al, cl ; 起始扇区的中八位
    out dx, al

    inc dx; 0x1f5
    shr ecx, 8 ; 将ecx向右移八位
    mov al, cl ; 起始扇区的高八位
    out dx, al

    inc dx; 0x1f6
    shr ecx, 8
    and cl, 0b1111;将高四位为0

    mov al, 0b1110_0000;
    or al, cl
    out dx, al ; 主盘 - LBA 模式

    inc dx; 0x1f7
    mov al ,0x20 ; 读硬盘
    out dx, al

    xor ecx, ecx; 将ecx清空
    mov cl, bl ; 得到读写扇区的数量

    .read:
        push cx ; 保存cx
        call .waits ; 等待数据准备完毕
        call .reads ; 读取一个扇区
        pop cx ; 恢复cx
        loop .read

    ret

    .waits:
        mov dx, 0x1f7
        .check:
            in al, dx
            jmp $+2; nop 直接跳转到下一行
            jmp $+2; 一点点延迟
            jmp $+2
            and al, 0b1000_1000
            cmp al, 0b0000_1000
            jnz .check
        ret

    .reads:
        mov dx, 0x1f0
        mov cx, 256 ; 一个扇区是512个字节，256个字
        .readw:
            in ax, dx
            jmp $+2; 一点点延迟
            jmp $+2; 
            jmp $+2
            mov [edi], ax ; edi是读取的目标内存
            add edi, 2
            loop .readw
        ret


code_selector equ (1 << 3)
data_selector equ (2 << 3)

memory_base equ 0 ; 内存开始的位置，基地址
memory_limit equ ((1024 * 1024 * 1024 * 4) / (1024 * 4)) - 1 ; 内存界限， 4G/4K - 1

gdt_ptr:
    dw (gdt_end - gdt_base) - 1
    dd gdt_base

gdt_base:
    ; db: define byte 8bit
    ; dw: define word 16bit
    ; dd:  define dword(double word), 双字形  32bit
    dd 0, 0 ; 8个字节为0，代表NULL 描述符,intel cpu要求的
gdt_code:
    dw memory_limit & 0xffff ; 段界限 0~15位
    dw memory_base & 0xffff ; 基地址 0~15位
    db (memory_base >> 16) & 0xff; 基地址 16~23位
    db 0b_1_00_1_1_0_1_0 ; 存在 - dpl 0 - S _ 代码 - 非依从 - 可读 - 没有被访问过
    db 0b1_1_0_0_0000 | (memory_limit >> 16) & 0xf;        ; 4k - 32bit - 不是64位 - aviailable随便写 - 段界限的16~19位
    db (memory_base >> 24) & 0xff ; 基地址24~31位
gdt_data:
    dw memory_limit & 0xffff ; 段界限 0~15位
    dw memory_base & 0xffff ; 基地址 0~15位
    db (memory_base >> 16) & 0xff; 基地址 16~23位
    db 0b_1_00_1_0_0_1_0 ; 存在 - dpl 0 - S _ 数据 - 向上 - 可写 - 没有被访问过
    db 0b1_1_0_0_0000 | (memory_limit >> 16) & 0xf;        ; 4k - 32bit - 不是64位 - aviailable随便写 - 段界限的16~19位
    db (memory_base >> 24) & 0xff ; 基地址24~31位
gdt_end:
ards_count:
    dw 0
ards_buffer:

